#include "stdafx.h"
#include "FileIO.h"
#include "win32exception.h"

static bool DoesVolumeSupportSparseFiles(HANDLE hFile)
{
	BOOL fRet = FALSE;
	DWORD dwLastError = 0;

	DWORD dwFileSystemFlags = 0;
	fRet = GetVolumeInformationByHandleW(hFile, NULL, 0, NULL, NULL, &dwFileSystemFlags, NULL, 0);
	if (FALSE == fRet)
	{
		dwLastError = GetLastError();
		throw Win32Exception("GetVolumeInformationByHandleW failure", dwLastError);
	}

	if (dwFileSystemFlags & FILE_SUPPORTS_SPARSE_FILES)
	{
		return true;
	}

	return false;
}

static HANDLE OpenFileForWrite(const std::wstring & strFile, bool fCreateNewFile)
{
	DWORD dwLastError = 0;

	HANDLE hFile = INVALID_HANDLE_VALUE;

	DWORD dwCreationDisposition = OPEN_EXISTING;
	if (fCreateNewFile)
	{
		dwCreationDisposition = CREATE_NEW;
	}

	hFile = CreateFileW(strFile.c_str(), FILE_WRITE_ACCESS, 0, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		dwLastError = GetLastError();
		throw Win32Exception("Opening file failed", dwLastError);
	}

	return hFile;
}

static void SetFileHandleSparse(HANDLE hFile, bool fSetSparse)
{
	BOOL fRet = FALSE;
	DWORD dwLastError = 0;

	FILE_SET_SPARSE_BUFFER setSparseBuffer;
	BOOLEAN bSparse = fSetSparse ? TRUE : FALSE;
	setSparseBuffer.SetSparse = bSparse;

	DWORD dwBytes = 0;
	fRet = DeviceIoControl(hFile, FSCTL_SET_SPARSE, &setSparseBuffer, sizeof(setSparseBuffer), NULL, 0, &dwBytes, NULL);
	if (!fRet)
	{
		dwLastError = GetLastError();
		throw Win32Exception("can't set file sparse-ness", dwLastError);
	}
}

static void SetFileSparse(const std::wstring & filename, bool fSetSparse)
{
	BOOL fRet = FALSE;
	DWORD dwLastError = 0;

	HANDLE hFile = OpenFileForWrite(filename, false);

	if (!DoesVolumeSupportSparseFiles(hFile))
	{
		throw std::exception("volume doesn't support sparse files");
	}

	BY_HANDLE_FILE_INFORMATION fileInformation;
	fRet = GetFileInformationByHandle(hFile, &fileInformation);
	if (!fRet)
	{
		dwLastError = GetLastError();
		throw Win32Exception("can't get file information", dwLastError);
	}

	bool fFileIsSparse = !!(FILE_ATTRIBUTE_SPARSE_FILE & fileInformation.dwFileAttributes);
	if (fFileIsSparse && fSetSparse)
	{
		throw std::exception("asked to make an already sparse file sparse.");
	}
	if (!fFileIsSparse && !fSetSparse)
	{
		throw std::exception("asked to make a not-sparse file not-sparse.");
	}

	SetFileHandleSparse(hFile, fSetSparse);

	FileIO::CloseFile(&hFile);
}

namespace FileIO
{
	LARGE_INTEGER GetFileSize(const std::wstring & filename)
	{
		BOOL fRet = FALSE;
		DWORD dwLastError = 0;

		LARGE_INTEGER liFileSize;
		liFileSize.QuadPart = 0;

		HANDLE hFile = OpenFileRead(filename);

		fRet = GetFileSizeEx(hFile, &liFileSize);
		if (!fRet)
		{
			dwLastError = GetLastError();
			throw Win32Exception("can't retrieve file size", dwLastError);
		}

		CloseFile(&hFile);
		return liFileSize;
	}

	void SetExistingFileSparse(const std::wstring & filename)
	{
		printf("%S marking file as sparse\r\n", filename.c_str());
		SetFileSparse(filename, true);
	}

	void CreateNewFileAsSparseWithSize(const std::wstring & filename, LARGE_INTEGER liFileSize)
	{
		printf("%S creating file as sparse\r\n", filename.c_str());
		BOOL fRet = FALSE;
		DWORD dwLastError = 0;

		// create the file
		HANDLE hFile = OpenFileForWrite(filename, true);
		CloseFile(&hFile);

		// mark it as sparse
		SetFileSparse(filename, true);

		// set the file length
		hFile = OpenFileForWrite(filename, false);
		fRet = SetFilePointerEx(hFile, liFileSize, NULL, FILE_BEGIN);
		if (!fRet)
		{
			dwLastError = GetLastError();
			throw Win32Exception("can't set file pointer for setting file length", dwLastError);
		}

		fRet = SetEndOfFile(hFile);
		if (!fRet)
		{
			dwLastError = GetLastError();
			throw Win32Exception("can't set end of file", dwLastError);
		}

		CloseFile(&hFile);
	}

	void SetExistingFileNotSparse(const std::wstring & filename)
	{
		printf("%S marking file as not-sparse\r\n", filename.c_str());
		SetFileSparse(filename, false);
	}

	void MarkSparseRange(const std::wstring & filename, ULONGLONG begin, ULONGLONG size)
	{
		printf("%S marking sparse at offset: %lld bytes: %lld\r\n", filename.c_str(), begin, size);
		BOOL fRet = FALSE;
		DWORD dwLastError = 0;

		LARGE_INTEGER liBegin;
		LARGE_INTEGER liEnd;

		if (begin > LLONG_MAX)
		{
			throw std::exception("starting offset too big");
		}

		if (size > LLONG_MAX)
		{
			throw std::exception("size too big");
		}

		if ((begin + size) > LLONG_MAX)
		{
			throw std::exception("resulting offset too big");
		}

		liBegin.QuadPart = begin;
		liEnd.QuadPart = begin + size;

		FILE_ZERO_DATA_INFORMATION fileZeroData;
		fileZeroData.FileOffset = liBegin;
		fileZeroData.BeyondFinalZero = liEnd;

		HANDLE hFile = OpenFileForWrite(filename, false);

		DWORD dwBytesReturned = 0;

		fRet = DeviceIoControl(hFile, FSCTL_SET_ZERO_DATA, &fileZeroData, sizeof(fileZeroData), NULL, 0, &dwBytesReturned, NULL);
		if (!fRet)
		{
			dwLastError = GetLastError();
			throw Win32Exception("FSCTL_SET_ZERO_DATA failed", dwLastError);
		}

		CloseFile(&hFile);
	}

	HANDLE OpenFileRead(const std::wstring & filename)
	{
		DWORD dwLastError = 0;

		HANDLE hFile = INVALID_HANDLE_VALUE;

		hFile = CreateFileW(filename.c_str(), FILE_READ_ACCESS, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			dwLastError = GetLastError();
			throw Win32Exception("Opening file for read failed", dwLastError);
		}

		return hFile;
	}

	HANDLE OpenFileWrite(const std::wstring & filename)
	{
		return OpenFileForWrite(filename, false);
	}

	void CloseFile(HANDLE * phFile)
	{
		BOOL fRet = FALSE;
		DWORD dwLastError = 0;

		fRet = CloseHandle(*phFile);
		if (!fRet)
		{
			dwLastError = GetLastError();
			throw Win32Exception("can't close file handle??", dwLastError);
		}

		*phFile = INVALID_HANDLE_VALUE;
	}

	void DeleteFile(const std::wstring & filename)
	{
		printf("%S deleting file\r\n", filename.c_str());

		BOOL fRet = FALSE;
		DWORD dwLastError = 0;

		fRet = ::DeleteFileW(filename.c_str());
		if (!fRet)
		{
			dwLastError = GetLastError();
			throw Win32Exception("can't delete file", dwLastError);
		}
	}
}

